/*
 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.plaf.nimbus;

import javax.swing.Painter;

import javax.swing.JComponent;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.synth.ColorType;

import static javax.swing.plaf.synth.SynthConstants.*;

import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthPainter;
import javax.swing.plaf.synth.SynthStyle;
import java.awt.Color;
import java.awt.Font;
import java.awt.Insets;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * <p>A SynthStyle implementation used by Nimbus. Each Region that has been
 * registered with the NimbusLookAndFeel will have an associated NimbusStyle.
 * Third party components that are registered with the NimbusLookAndFeel will
 * therefore be handed a NimbusStyle from the look and feel from the
 * #getStyle(JComponent, Region) method.</p>
 *
 * <p>This class properly reads and retrieves values placed in the UIDefaults
 * according to the standard Nimbus naming conventions. It will create and
 * retrieve painters, fonts, colors, and other data stored there.</p>
 *
 * <p>NimbusStyle also supports the ability to override settings on a per
 * component basis. NimbusStyle checks the component's client property map for
 * "Nimbus.Overrides". If the value associated with this key is an instance of
 * UIDefaults, then the values in that defaults table will override the standard
 * Nimbus defaults in UIManager, but for that component instance only.</p>
 *
 * <p>Optionally, you may specify the client property
 * "Nimbus.Overrides.InheritDefaults". If true, this client property indicates
 * that the defaults located in UIManager should first be read, and then
 * replaced with defaults located in the component client properties. If false,
 * then only the defaults located in the component client property map will
 * be used. If not specified, it is assumed to be true.</p>
 *
 * <p>You must specify "Nimbus.Overrides" for "Nimbus.Overrides.InheritDefaults"
 * to have any effect. "Nimbus.Overrides" indicates whether there are any
 * overrides, while "Nimbus.Overrides.InheritDefaults" indicates whether those
 * overrides should first be initialized with the defaults from UIManager.</p>
 *
 * <p>The NimbusStyle is reloaded whenever a property change event is fired
 * for a component for "Nimbus.Overrides" or "Nimbus.Overrides.InheritDefaults".
 * So for example, setting a new UIDefaults on a component would cause the
 * style to be reloaded.</p>
 *
 * <p>The values are only read out of UIManager once, and then cached. If
 * you need to read the values again (for example, if the UI is being reloaded),
 * then discard this NimbusStyle and read a new one from NimbusLookAndFeel
 * using NimbusLookAndFeel.getStyle.</p>
 *
 * <p>The primary API of interest in this class for 3rd party component authors
 * are the three methods which retrieve painters: #getBackgroundPainter,
 * #getForegroundPainter, and #getBorderPainter.</p>
 *
 * <p>NimbusStyle allows you to specify custom states, or modify the order of
 * states. Synth (and thus Nimbus) has the concept of a "state". For example,
 * a JButton might be in the "MOUSE_OVER" state, or the "ENABLED" state, or the
 * "DISABLED" state. These are all "standard" states which are defined in synth,
 * and which apply to all synth Regions.</p>
 *
 * <p>Sometimes, however, you need to have a custom state. For example, you
 * want JButton to render differently if it's parent is a JToolbar. In Nimbus,
 * you specify these custom states by including a special key in UIDefaults.
 * The following UIDefaults entries define three states for this button:</p>
 *
 * <pre><code>
 *     JButton.States = Enabled, Disabled, Toolbar
 *     JButton[Enabled].backgroundPainter = somePainter
 *     JButton[Disabled].background = BLUE
 *     JButton[Toolbar].backgroundPainter = someOtherPaint
 * </code></pre>
 *
 * <p>As you can see, the <code>JButton.States</code> entry lists the states
 * that the JButton style will support. You then specify the settings for
 * each state. If you do not specify the <code>JButton.States</code> entry,
 * then the standard Synth states will be assumed. If you specify the entry
 * but the list of states is empty or null, then the standard synth states
 * will be assumed.</p>
 *
 * @author Richard Bair
 * @author Jasper Potts
 */
public final class NimbusStyle extends SynthStyle {

  /* Keys and scales for large/small/mini components, based on Apples sizes */
  public static final String LARGE_KEY = "large";
  public static final String SMALL_KEY = "small";
  public static final String MINI_KEY = "mini";
  public static final double LARGE_SCALE = 1.15;
  public static final double SMALL_SCALE = 0.857;
  public static final double MINI_SCALE = 0.714;

  /**
   * Special constant used for performance reasons during the get() method.
   * If get() runs through all of the search locations and determines that
   * there is no value, then NULL will be placed into the values map. This way
   * on subsequent lookups it will simply extract NULL, see it, and return
   * null rather than continuing the lookup procedure.
   */
  private static final Object NULL = '\0';
  /**
   * <p>The Color to return from getColorForState if it would otherwise have
   * returned null.</p>
   *
   * <p>Returning null from getColorForState is a very bad thing, as it causes
   * the AWT peer for the component to install a SystemColor, which is not a
   * UIResource. As a result, if <code>null</code> is returned from
   * getColorForState, then thereafter the color is not updated for other
   * states or on LAF changes or updates. This DEFAULT_COLOR is used to
   * ensure that a ColorUIResource is always returned from
   * getColorForState.</p>
   */
  private static final Color DEFAULT_COLOR = new ColorUIResource(Color.BLACK);
  /**
   * Simple Comparator for ordering the RuntimeStates according to their
   * rank.
   */
  private static final Comparator<RuntimeState> STATE_COMPARATOR =
      new Comparator<RuntimeState>() {
        @Override
        public int compare(RuntimeState a, RuntimeState b) {
          return a.state - b.state;
        }
      };
  /**
   * The prefix for the component or region that this NimbusStyle
   * represents. This prefix is used to lookup state in the UIManager.
   * It should be something like Button or Slider.Thumb or "MyButton" or
   * ComboBox."ComboBox.arrowButton" or "MyComboBox"."ComboBox.arrowButton"
   */
  private String prefix;
  /**
   * The SynthPainter that will be returned from this NimbusStyle. The
   * SynthPainter returned will be a SynthPainterImpl, which will in turn
   * delegate back to this NimbusStyle for the proper Painter (not
   * SynthPainter) to use for painting the foreground, background, or border.
   */
  private SynthPainter painter;
  /**
   * Data structure containing all of the defaults, insets, states, and other
   * values associated with this style. This instance refers to default
   * values, and are used when no overrides are discovered in the client
   * properties of a component. These values are lazily created on first
   * access.
   */
  private Values values;

  /**
   * A temporary CacheKey used to perform lookups. This pattern avoids
   * creating useless garbage keys, or concatenating strings, etc.
   */
  private CacheKey tmpKey = new CacheKey("", 0);

  /**
   * Some NimbusStyles are created for a specific component only. In Nimbus,
   * this happens whenever the component has as a client property a
   * UIDefaults which overrides (or supplements) those defaults found in
   * UIManager.
   */
  private WeakReference<JComponent> component;

  /**
   * Create a new NimbusStyle. Only the prefix must be supplied. At the
   * appropriate time, installDefaults will be called. At that point, all of
   * the state information will be pulled from UIManager and stored locally
   * within this style.
   *
   * @param prefix Something like Button or Slider.Thumb or org.jdesktop.swingx.JXStatusBar or
   * ComboBox."ComboBox.arrowButton"
   * @param c an optional reference to a component that this NimbusStyle should be associated with.
   * This is only used when the component has Nimbus overrides registered in its client properties
   * and should be null otherwise.
   */
  NimbusStyle(String prefix, JComponent c) {
    if (c != null) {
      this.component = new WeakReference<JComponent>(c);
    }
    this.prefix = prefix;
    this.painter = new SynthPainterImpl(this);
  }

  /**
   * {@inheritDoc}
   *
   * Overridden to cause this style to populate itself with data from
   * UIDefaults, if necessary.
   */
  @Override
  public void installDefaults(SynthContext ctx) {
    validate();

    //delegate to the superclass to install defaults such as background,
    //foreground, font, and opaque onto the swing component.
    super.installDefaults(ctx);
  }

  /**
   * Pulls data out of UIDefaults, if it has not done so already, and sets
   * up the internal state.
   */
  private void validate() {
    // a non-null values object is the flag we use to determine whether
    // to reparse from UIManager.
    if (values != null) {
      return;
    }

    // reconstruct this NimbusStyle based on the entries in the UIManager
    // and possibly based on any overrides within the component's
    // client properties (assuming such a component exists and contains
    // any Nimbus.Overrides)
    values = new Values();

    Map<String, Object> defaults =
        ((NimbusLookAndFeel) UIManager.getLookAndFeel()).
            getDefaultsForPrefix(prefix);

    // inspect the client properties for the key "Nimbus.Overrides". If the
    // value is an instance of UIDefaults, then these defaults are used
    // in place of, or in addition to, the defaults in UIManager.
    if (component != null) {
      // We know component.get() is non-null here, as if the component
      // were GC'ed, we wouldn't be processing its style.
      Object o = component.get().getClientProperty("Nimbus.Overrides");
      if (o instanceof UIDefaults) {
        Object i = component.get().getClientProperty(
            "Nimbus.Overrides.InheritDefaults");
        boolean inherit = i instanceof Boolean ? (Boolean) i : true;
        UIDefaults d = (UIDefaults) o;
        TreeMap<String, Object> map = new TreeMap<String, Object>();
        for (Object obj : d.keySet()) {
          if (obj instanceof String) {
            String key = (String) obj;
            if (key.startsWith(prefix)) {
              map.put(key, d.get(key));
            }
          }
        }
        if (inherit) {
          defaults.putAll(map);
        } else {
          defaults = map;
        }
      }
    }

    //a list of the different types of states used by this style. This
    //list may contain only "standard" states (those defined by Synth),
    //or it may contain custom states, or it may contain only "standard"
    //states but list them in a non-standard order.
    List<State> states = new ArrayList<State>();
    //a map of state name to code
    Map<String, Integer> stateCodes = new HashMap<String, Integer>();
    //This is a list of runtime "state" context objects. These contain
    //the values associated with each state.
    List<RuntimeState> runtimeStates = new ArrayList<RuntimeState>();

    //determine whether there are any custom states, or custom state
    //order. If so, then read all those custom states and define the
    //"values" stateTypes to be a non-null array.
    //Otherwise, let the "values" stateTypes be null to indicate that
    //there are no custom states or custom state ordering
    String statesString = (String) defaults.get(prefix + ".States");
    if (statesString != null) {
      String s[] = statesString.split(",");
      for (int i = 0; i < s.length; i++) {
        s[i] = s[i].trim();
        if (!State.isStandardStateName(s[i])) {
          //this is a non-standard state name, so look for the
          //custom state associated with it
          String stateName = prefix + "." + s[i];
          State customState = (State) defaults.get(stateName);
          if (customState != null) {
            states.add(customState);
          }
        } else {
          states.add(State.getStandardState(s[i]));
        }
      }

      //if there were any states defined, then set the stateTypes array
      //to be non-null. Otherwise, leave it null (meaning, use the
      //standard synth states).
      if (states.size() > 0) {
        values.stateTypes = states.toArray(new State[states.size()]);
      }

      //assign codes for each of the state types
      int code = 1;
      for (State state : states) {
        stateCodes.put(state.getName(), code);
        code <<= 1;
      }
    } else {
      //since there were no custom states defined, setup the list of
      //standard synth states. Note that the "v.stateTypes" is not
      //being set here, indicating that at runtime the state selection
      //routines should use standard synth states instead of custom
      //states. I do need to popuplate this temp list now though, so that
      //the remainder of this method will function as expected.
      states.add(State.Enabled);
      states.add(State.MouseOver);
      states.add(State.Pressed);
      states.add(State.Disabled);
      states.add(State.Focused);
      states.add(State.Selected);
      states.add(State.Default);

      //assign codes for the states
      stateCodes.put("Enabled", ENABLED);
      stateCodes.put("MouseOver", MOUSE_OVER);
      stateCodes.put("Pressed", PRESSED);
      stateCodes.put("Disabled", DISABLED);
      stateCodes.put("Focused", FOCUSED);
      stateCodes.put("Selected", SELECTED);
      stateCodes.put("Default", DEFAULT);
    }

    //Now iterate over all the keys in the defaults table
    for (String key : defaults.keySet()) {
      //The key is something like JButton.Enabled.backgroundPainter,
      //or JButton.States, or JButton.background.
      //Remove the "JButton." portion of the key
      String temp = key.substring(prefix.length());
      //if there is a " or : then we skip it because it is a subregion
      //of some kind
      if (temp.indexOf('"') != -1 || temp.indexOf(':') != -1) {
        continue;
      }
      //remove the separator
      temp = temp.substring(1);
      //At this point, temp may be any of the following:
      //background
      //[Enabled].background
      //[Enabled+MouseOver].background
      //property.foo

      //parse out the states and the property
      String stateString = null;
      String property = null;
      int bracketIndex = temp.indexOf(']');
      if (bracketIndex < 0) {
        //there is not a state string, so property = temp
        property = temp;
      } else {
        stateString = temp.substring(0, bracketIndex);
        property = temp.substring(bracketIndex + 2);
      }

      //now that I have the state (if any) and the property, get the
      //value for this property and install it where it belongs
      if (stateString == null) {
        //there was no state, just a property. Check for the custom
        //"contentMargins" property (which is handled specially by
        //Synth/Nimbus). Also check for the property being "States",
        //in which case it is not a real property and should be ignored.
        //otherwise, assume it is a property and install it on the
        //values object
        if ("contentMargins".equals(property)) {
          values.contentMargins = (Insets) defaults.get(key);
        } else if ("States".equals(property)) {
          //ignore
        } else {
          values.defaults.put(property, defaults.get(key));
        }
      } else {
        //it is possible that the developer has a malformed UIDefaults
        //entry, such that something was specified in the place of
        //the State portion of the key but it wasn't a state. In this
        //case, skip will be set to true
        boolean skip = false;
        //this variable keeps track of the int value associated with
        //the state. See SynthState for details.
        int componentState = 0;
        //Multiple states may be specified in the string, such as
        //Enabled+MouseOver
        String[] stateParts = stateString.split("\\+");
        //For each state, we need to find the State object associated
        //with it, or skip it if it cannot be found.
        for (String s : stateParts) {
          if (stateCodes.containsKey(s)) {
            componentState |= stateCodes.get(s);
          } else {
            //Was not a state. Maybe it was a subregion or something
            //skip it.
            skip = true;
            break;
          }
        }

        if (skip) {
          continue;
        }

        //find the RuntimeState for this State
        RuntimeState rs = null;
        for (RuntimeState s : runtimeStates) {
          if (s.state == componentState) {
            rs = s;
            break;
          }
        }

        //couldn't find the runtime state, so create a new one
        if (rs == null) {
          rs = new RuntimeState(componentState, stateString);
          runtimeStates.add(rs);
        }

        //check for a couple special properties, such as for the
        //painters. If these are found, then set the specially on
        //the runtime state. Else, it is just a normal property,
        //so put it in the UIDefaults associated with that runtime
        //state
        if ("backgroundPainter".equals(property)) {
          rs.backgroundPainter = getPainter(defaults, key);
        } else if ("foregroundPainter".equals(property)) {
          rs.foregroundPainter = getPainter(defaults, key);
        } else if ("borderPainter".equals(property)) {
          rs.borderPainter = getPainter(defaults, key);
        } else {
          rs.defaults.put(property, defaults.get(key));
        }
      }
    }

    //now that I've collected all the runtime states, I'll sort them based
    //on their integer "state" (see SynthState for how this works).
    Collections.sort(runtimeStates, STATE_COMPARATOR);

    //finally, set the array of runtime states on the values object
    values.states = runtimeStates.toArray(new RuntimeState[runtimeStates.size()]);
  }

  private Painter getPainter(Map<String, Object> defaults, String key) {
    Object p = defaults.get(key);
    if (p instanceof UIDefaults.LazyValue) {
      p = ((UIDefaults.LazyValue) p).createValue(UIManager.getDefaults());
    }
    return (p instanceof Painter ? (Painter) p : null);
  }

  /**
   * {@inheritDoc}
   *
   * Overridden to cause this style to populate itself with data from
   * UIDefaults, if necessary.
   */
  @Override
  public Insets getInsets(SynthContext ctx, Insets in) {
    if (in == null) {
      in = new Insets(0, 0, 0, 0);
    }

    Values v = getValues(ctx);

    if (v.contentMargins == null) {
      in.bottom = in.top = in.left = in.right = 0;
      return in;
    } else {
      in.bottom = v.contentMargins.bottom;
      in.top = v.contentMargins.top;
      in.left = v.contentMargins.left;
      in.right = v.contentMargins.right;
      // Account for scale
      // The key "JComponent.sizeVariant" is used to match Apple's LAF
      String scaleKey = (String) ctx.getComponent().getClientProperty(
          "JComponent.sizeVariant");
      if (scaleKey != null) {
        if (LARGE_KEY.equals(scaleKey)) {
          in.bottom *= LARGE_SCALE;
          in.top *= LARGE_SCALE;
          in.left *= LARGE_SCALE;
          in.right *= LARGE_SCALE;
        } else if (SMALL_KEY.equals(scaleKey)) {
          in.bottom *= SMALL_SCALE;
          in.top *= SMALL_SCALE;
          in.left *= SMALL_SCALE;
          in.right *= SMALL_SCALE;
        } else if (MINI_KEY.equals(scaleKey)) {
          in.bottom *= MINI_SCALE;
          in.top *= MINI_SCALE;
          in.left *= MINI_SCALE;
          in.right *= MINI_SCALE;
        }
      }
      return in;
    }
  }

  /**
   * {@inheritDoc}
   *
   * <p>Overridden to cause this style to populate itself with data from
   * UIDefaults, if necessary.</p>
   *
   * <p>In addition, NimbusStyle handles ColorTypes slightly differently from
   * Synth.</p>
   * <ul>
   * <li>ColorType.BACKGROUND will equate to the color stored in UIDefaults
   * named "background".</li>
   * <li>ColorType.TEXT_BACKGROUND will equate to the color stored in
   * UIDefaults named "textBackground".</li>
   * <li>ColorType.FOREGROUND will equate to the color stored in UIDefaults
   * named "textForeground".</li>
   * <li>ColorType.TEXT_FOREGROUND will equate to the color stored in
   * UIDefaults named "textForeground".</li>
   * </ul>
   */
  @Override
  protected Color getColorForState(SynthContext ctx, ColorType type) {
    String key = null;
    if (type == ColorType.BACKGROUND) {
      key = "background";
    } else if (type == ColorType.FOREGROUND) {
      //map FOREGROUND as TEXT_FOREGROUND
      key = "textForeground";
    } else if (type == ColorType.TEXT_BACKGROUND) {
      key = "textBackground";
    } else if (type == ColorType.TEXT_FOREGROUND) {
      key = "textForeground";
    } else if (type == ColorType.FOCUS) {
      key = "focus";
    } else if (type != null) {
      key = type.toString();
    } else {
      return DEFAULT_COLOR;
    }
    Color c = (Color) get(ctx, key);
    //if all else fails, return a default color (which is a ColorUIResource)
    if (c == null) {
      c = DEFAULT_COLOR;
    }
    return c;
  }

  /**
   * {@inheritDoc}
   *
   * Overridden to cause this style to populate itself with data from
   * UIDefaults, if necessary. If a value named "font" is not found in
   * UIDefaults, then the "defaultFont" font in UIDefaults will be returned
   * instead.
   */
  @Override
  protected Font getFontForState(SynthContext ctx) {
    Font f = (Font) get(ctx, "font");
    if (f == null) {
      f = UIManager.getFont("defaultFont");
    }

    // Account for scale
    // The key "JComponent.sizeVariant" is used to match Apple's LAF
    String scaleKey = (String) ctx.getComponent().getClientProperty(
        "JComponent.sizeVariant");
    if (scaleKey != null) {
      if (LARGE_KEY.equals(scaleKey)) {
        f = f.deriveFont(Math.round(f.getSize2D() * LARGE_SCALE));
      } else if (SMALL_KEY.equals(scaleKey)) {
        f = f.deriveFont(Math.round(f.getSize2D() * SMALL_SCALE));
      } else if (MINI_KEY.equals(scaleKey)) {
        f = f.deriveFont(Math.round(f.getSize2D() * MINI_SCALE));
      }
    }
    return f;
  }

  /**
   * {@inheritDoc}
   *
   * Returns the SynthPainter for this style, which ends up delegating to
   * the Painters installed in this style.
   */
  @Override
  public SynthPainter getPainter(SynthContext ctx) {
    return painter;
  }

  /**
   * {@inheritDoc}
   *
   * Overridden to cause this style to populate itself with data from
   * UIDefaults, if necessary. If opacity is not specified in UI defaults,
   * then it defaults to being non-opaque.
   */
  @Override
  public boolean isOpaque(SynthContext ctx) {
    // Force Table CellRenderers to be opaque
    if ("Table.cellRenderer".equals(ctx.getComponent().getName())) {
      return true;
    }
    Boolean opaque = (Boolean) get(ctx, "opaque");
    return opaque == null ? false : opaque;
  }

  /**
   * {@inheritDoc}
   *
   * <p>Overridden to cause this style to populate itself with data from
   * UIDefaults, if necessary.</p>
   *
   * <p>Properties in UIDefaults may be specified in a chained manner. For
   * example:
   * <pre>
   * background
   * Button.opacity
   * Button.Enabled.foreground
   * Button.Enabled+Selected.background
   * </pre>
   *
   * <p>In this example, suppose you were in the Enabled+Selected state and
   * searched for "foreground". In this case, we first check for
   * Button.Enabled+Selected.foreground, but no such color exists. We then
   * fall back to the next valid state, in this case,
   * Button.Enabled.foreground, and have a match. So we return it.</p>
   *
   * <p>Again, if we were in the state Enabled and looked for "background", we
   * wouldn't find it in Button.Enabled, or in Button, but would at the top
   * level in UIManager. So we return that value.</p>
   *
   * <p>One special note: the "key" passed to this method could be of the form
   * "background" or "Button.background" where "Button" equals the prefix
   * passed to the NimbusStyle constructor. In either case, it looks for
   * "background".</p>
   *
   * @param key must not be null
   */
  @Override
  public Object get(SynthContext ctx, Object key) {
    Values v = getValues(ctx);

    // strip off the prefix, if there is one.
    String fullKey = key.toString();
    String partialKey = fullKey.substring(fullKey.indexOf(".") + 1);

    Object obj = null;
    int xstate = getExtendedState(ctx, v);

    // check the cache
    tmpKey.init(partialKey, xstate);
    obj = v.cache.get(tmpKey);
    boolean wasInCache = obj != null;
    if (!wasInCache) {
      // Search exact matching states and then lesser matching states
      RuntimeState s = null;
      int[] lastIndex = new int[]{-1};
      while (obj == null &&
          (s = getNextState(v.states, lastIndex, xstate)) != null) {
        obj = s.defaults.get(partialKey);
      }
      // Search Region Defaults
      if (obj == null && v.defaults != null) {
        obj = v.defaults.get(partialKey);
      }
      // return found object
      // Search UIManager Defaults
      if (obj == null) {
        obj = UIManager.get(fullKey);
      }
      // Search Synth Defaults for InputMaps
      if (obj == null && partialKey.equals("focusInputMap")) {
        obj = super.get(ctx, fullKey);
      }
      // if all we got was a null, store this fact for later use
      v.cache.put(new CacheKey(partialKey, xstate),
          obj == null ? NULL : obj);
    }
    // return found object
    return obj == NULL ? null : obj;
  }

  /**
   * Gets the appropriate background Painter, if there is one, for the state
   * specified in the given SynthContext. This method does appropriate
   * fallback searching, as described in #get.
   *
   * @param ctx The SynthContext. Must not be null.
   * @return The background painter associated for the given state, or null if none could be found.
   */
  public Painter getBackgroundPainter(SynthContext ctx) {
    Values v = getValues(ctx);
    int xstate = getExtendedState(ctx, v);
    Painter p = null;

    // check the cache
    tmpKey.init("backgroundPainter$$instance", xstate);
    p = (Painter) v.cache.get(tmpKey);
    if (p != null) {
      return p;
    }

    // not in cache, so lookup and store in cache
    RuntimeState s = null;
    int[] lastIndex = new int[]{-1};
    while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
      if (s.backgroundPainter != null) {
        p = s.backgroundPainter;
        break;
      }
    }
    if (p == null) {
      p = (Painter) get(ctx, "backgroundPainter");
    }
    if (p != null) {
      v.cache.put(new CacheKey("backgroundPainter$$instance", xstate), p);
    }
    return p;
  }

  /**
   * Gets the appropriate foreground Painter, if there is one, for the state
   * specified in the given SynthContext. This method does appropriate
   * fallback searching, as described in #get.
   *
   * @param ctx The SynthContext. Must not be null.
   * @return The foreground painter associated for the given state, or null if none could be found.
   */
  public Painter getForegroundPainter(SynthContext ctx) {
    Values v = getValues(ctx);
    int xstate = getExtendedState(ctx, v);
    Painter p = null;

    // check the cache
    tmpKey.init("foregroundPainter$$instance", xstate);
    p = (Painter) v.cache.get(tmpKey);
    if (p != null) {
      return p;
    }

    // not in cache, so lookup and store in cache
    RuntimeState s = null;
    int[] lastIndex = new int[]{-1};
    while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
      if (s.foregroundPainter != null) {
        p = s.foregroundPainter;
        break;
      }
    }
    if (p == null) {
      p = (Painter) get(ctx, "foregroundPainter");
    }
    if (p != null) {
      v.cache.put(new CacheKey("foregroundPainter$$instance", xstate), p);
    }
    return p;
  }

  /**
   * Gets the appropriate border Painter, if there is one, for the state
   * specified in the given SynthContext. This method does appropriate
   * fallback searching, as described in #get.
   *
   * @param ctx The SynthContext. Must not be null.
   * @return The border painter associated for the given state, or null if none could be found.
   */
  public Painter getBorderPainter(SynthContext ctx) {
    Values v = getValues(ctx);
    int xstate = getExtendedState(ctx, v);
    Painter p = null;

    // check the cache
    tmpKey.init("borderPainter$$instance", xstate);
    p = (Painter) v.cache.get(tmpKey);
    if (p != null) {
      return p;
    }

    // not in cache, so lookup and store in cache
    RuntimeState s = null;
    int[] lastIndex = new int[]{-1};
    while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
      if (s.borderPainter != null) {
        p = s.borderPainter;
        break;
      }
    }
    if (p == null) {
      p = (Painter) get(ctx, "borderPainter");
    }
    if (p != null) {
      v.cache.put(new CacheKey("borderPainter$$instance", xstate), p);
    }
    return p;
  }

  /**
   * Utility method which returns the proper Values based on the given
   * SynthContext. Ensures that parsing of the values has occurred, or
   * reoccurs as necessary.
   *
   * @param ctx The SynthContext
   * @return a non-null values reference
   */
  private Values getValues(SynthContext ctx) {
    validate();
    return values;
  }

  /**
   * Simple utility method that searches the given array of Strings for the
   * given string. This method is only called from getExtendedState if
   * the developer has specified a specific state for the component to be
   * in (ie, has "wedged" the component in that state) by specifying
   * they client property "Nimbus.State".
   *
   * @param names a non-null array of strings
   * @param name the name to look for in the array
   * @return true or false based on whether the given name is in the array
   */
  private boolean contains(String[] names, String name) {
    assert name != null;
    for (int i = 0; i < names.length; i++) {
      if (name.equals(names[i])) {
        return true;
      }
    }
    return false;
  }

  /**
   * <p>Gets the extended state for a given synth context. Nimbus supports the
   * ability to define custom states. The algorithm used for choosing what
   * style information to use for a given state requires a single integer
   * bit string where each bit in the integer represents a different state
   * that the component is in. This method uses the componentState as
   * reported in the SynthContext, in addition to custom states, to determine
   * what this extended state is.</p>
   *
   * <p>In addition, this method checks the component in the given context
   * for a client property called "Nimbus.State". If one exists, then it will
   * decompose the String associated with that property to determine what
   * state to return. In this way, the developer can force a component to be
   * in a specific state, regardless of what the "real" state of the component
   * is.</p>
   *
   * <p>The string associated with "Nimbus.State" would be of the form:
   * <pre>Enabled+CustomState+MouseOver</pre></p>
   */
  private int getExtendedState(SynthContext ctx, Values v) {
    JComponent c = ctx.getComponent();
    int xstate = 0;
    int mask = 1;
    //check for the Nimbus.State client property
    //Performance NOTE: getClientProperty ends up inside a synchronized
    //block, so there is some potential for performance issues here, however
    //I'm not certain that there is one on a modern VM.
    Object property = c.getClientProperty("Nimbus.State");
    if (property != null) {
      String stateNames = property.toString();
      String[] states = stateNames.split("\\+");
      if (v.stateTypes == null) {
        // standard states only
        for (String stateStr : states) {
          State.StandardState s = State.getStandardState(stateStr);
          if (s != null) {
            xstate |= s.getState();
          }
        }
      } else {
        // custom states
        for (State s : v.stateTypes) {
          if (contains(states, s.getName())) {
            xstate |= mask;
          }
          mask <<= 1;
        }
      }
    } else {
      //if there are no custom states defined, then simply return the
      //state that Synth reported
      if (v.stateTypes == null) {
        return ctx.getComponentState();
      }

      //there are custom states on this values, so I'll have to iterate
      //over them all and return a custom extended state
      int state = ctx.getComponentState();
      for (State s : v.stateTypes) {
        if (s.isInState(c, state)) {
          xstate |= mask;
        }
        mask <<= 1;
      }
    }
    return xstate;
  }

  /**
   * <p>Gets the RuntimeState that most closely matches the state in the given
   * context, but is less specific than the given "lastState". Essentially,
   * this allows you to search for the next best state.</p>
   *
   * <p>For example, if you had the following three states:
   * <pre>
   * Enabled
   * Enabled+Pressed
   * Disabled
   * </pre>
   * And you wanted to find the state that best represented
   * ENABLED+PRESSED+FOCUSED and <code>lastState</code> was null (or an
   * empty array, or an array with a single int with index == -1), then
   * Enabled+Pressed would be returned. If you then call this method again but
   * pass the index of Enabled+Pressed as the "lastState", then
   * Enabled would be returned. If you call this method a third time and pass
   * the index of Enabled in as the <code>lastState</code>, then null would be
   * returned.</p>
   *
   * <p>The actual code path for determining the proper state is the same as
   * in Synth.</p>
   *
   * @param lastState a 1 element array, allowing me to do pass-by-reference.
   */
  private RuntimeState getNextState(RuntimeState[] states,
      int[] lastState,
      int xstate) {
    // Use the StateInfo with the most bits that matches that of state.
    // If there are none, then fallback to
    // the StateInfo with a state of 0, indicating it'll match anything.

    // Consider if we have 3 StateInfos a, b and c with states:
    // SELECTED, SELECTED | ENABLED, 0
    //
    // Input                          Return Value
    // -----                          ------------
    // SELECTED                       a
    // SELECTED | ENABLED             b
    // MOUSE_OVER                     c
    // SELECTED | ENABLED | FOCUSED   b
    // ENABLED                        c

    if (states != null && states.length > 0) {
      int bestCount = 0;
      int bestIndex = -1;
      int wildIndex = -1;

      //if xstate is 0, then search for the runtime state with component
      //state of 0. That is, find the exact match and return it.
      if (xstate == 0) {
        for (int counter = states.length - 1; counter >= 0; counter--) {
          if (states[counter].state == 0) {
            lastState[0] = counter;
            return states[counter];
          }
        }
        //an exact match couldn't be found, so there was no match.
        lastState[0] = -1;
        return null;
      }

      //xstate is some value != 0

      //determine from which index to start looking. If lastState[0] is -1
      //then we know to start from the end of the state array. Otherwise,
      //we start at the lastIndex - 1.
      int lastStateIndex = lastState == null || lastState[0] == -1 ?
          states.length : lastState[0];

      for (int counter = lastStateIndex - 1; counter >= 0; counter--) {
        int oState = states[counter].state;

        if (oState == 0) {
          if (wildIndex == -1) {
            wildIndex = counter;
          }
        } else if ((xstate & oState) == oState) {
          // This is key, we need to make sure all bits of the
          // StateInfo match, otherwise a StateInfo with
          // SELECTED | ENABLED would match ENABLED, which we
          // don't want.

          // This comes from BigInteger.bitCnt
          int bitCount = oState;
          bitCount -= (0xaaaaaaaa & bitCount) >>> 1;
          bitCount = (bitCount & 0x33333333) + ((bitCount >>> 2) &
              0x33333333);
          bitCount = bitCount + (bitCount >>> 4) & 0x0f0f0f0f;
          bitCount += bitCount >>> 8;
          bitCount += bitCount >>> 16;
          bitCount = bitCount & 0xff;
          if (bitCount > bestCount) {
            bestIndex = counter;
            bestCount = bitCount;
          }
        }
      }
      if (bestIndex != -1) {
        lastState[0] = bestIndex;
        return states[bestIndex];
      }
      if (wildIndex != -1) {
        lastState[0] = wildIndex;
        return states[wildIndex];
      }
    }
    lastState[0] = -1;
    return null;
  }

  /**
   * Contains values such as the UIDefaults and painters associated with
   * a state. Whereas <code>State</code> represents a distinct state that a
   * component can be in (such as Enabled), this class represents the colors,
   * fonts, painters, etc associated with some state for this
   * style.
   */
  private final class RuntimeState implements Cloneable {

    int state;
    Painter backgroundPainter;
    Painter foregroundPainter;
    Painter borderPainter;
    String stateName;
    UIDefaults defaults = new UIDefaults(10, .7f);

    private RuntimeState(int state, String stateName) {
      this.state = state;
      this.stateName = stateName;
    }

    @Override
    public String toString() {
      return stateName;
    }

    @Override
    public RuntimeState clone() {
      RuntimeState clone = new RuntimeState(state, stateName);
      clone.backgroundPainter = backgroundPainter;
      clone.foregroundPainter = foregroundPainter;
      clone.borderPainter = borderPainter;
      clone.defaults.putAll(defaults);
      return clone;
    }
  }

  /**
   * Essentially a struct of data for a style. A default instance of this
   * class is used by NimbusStyle. Additional instances exist for each
   * component that has overrides.
   */
  private static final class Values {

    /**
     * The list of State types. A State represents a type of state, such
     * as Enabled, Default, WindowFocused, etc. These can be custom states.
     */
    State[] stateTypes = null;
    /**
     * The list of actual runtime state representations. These can represent things such
     * as Enabled + Focused. Thus, they differ from States in that they contain
     * several states together, and have associated properties, data, etc.
     */
    RuntimeState[] states = null;
    /**
     * The content margins for this region.
     */
    Insets contentMargins;
    /**
     * Defaults on the region/component level.
     */
    UIDefaults defaults = new UIDefaults(10, .7f);
    /**
     * Simple cache. After a value has been looked up, it is stored
     * in this cache for later retrieval. The key is a concatenation of
     * the property being looked up, two dollar signs, and the extended
     * state. So for example:
     *
     * foo.bar$$2353
     */
    Map<CacheKey, Object> cache = new HashMap<CacheKey, Object>();
  }

  /**
   * This implementation presupposes that key is never null and that
   * the two keys being checked for equality are never null
   */
  private static final class CacheKey {

    private String key;
    private int xstate;

    CacheKey(Object key, int xstate) {
      init(key, xstate);
    }

    void init(Object key, int xstate) {
      this.key = key.toString();
      this.xstate = xstate;
    }

    @Override
    public boolean equals(Object obj) {
      final CacheKey other = (CacheKey) obj;
      if (obj == null) {
        return false;
      }
      if (this.xstate != other.xstate) {
        return false;
      }
      if (!this.key.equals(other.key)) {
        return false;
      }
      return true;
    }

    @Override
    public int hashCode() {
      int hash = 3;
      hash = 29 * hash + this.key.hashCode();
      hash = 29 * hash + this.xstate;
      return hash;
    }
  }
}
